#include "htmlGui.hpp"

namespace htmk 
{
	/*--------------------------------------------------------------------------------------------------------------
    * STATIC ATTRIBUTES
    *--------------------------------------------------------------------------------------------------------------*/ 
	htmlGui::Str_t htmlGui::m_localhost;   
	htmlGui::File_t htmlGui::m_fileInput;
	htmlGui::Config_t htmlGui::m_file_config;
    htmlGui::MapConfig_t htmlGui::m_file_maps;
	htmlGui::Str_t htmlGui::m_dir_sep;
	htmlGui::MapSB_t htmlGui::m_isBinaryFile;
	htmlGui::Str_t htmlGui::m_result_file_log;
	htmlGui::Str_t htmlGui::m_result_file_errors;
	htmlGui::CCStr_t htmlGui::m_unix_sep="/";
	htmlGui::CCStr_t htmlGui::m_win_sep="\\";
	htmlGui::CCStr_t htmlGui::m_win_dev_sep=":\\";
	htmlGui::CCStr_t htmlGui::m_url_sep="/";
	htmlGui::CCStr_t htmlGui::m_post="POST";
	htmlGui::CCStr_t htmlGui::m_get="GET";
	htmlGui::CCStr_t htmlGui::m_jar=".jar";
	htmlGui::CCStr_t htmlGui::m_html=".html";
	htmlGui::CCStr_t htmlGui::m_htm=".htm";
	htmlGui::CCStr_t htmlGui::m_css=".css";
	htmlGui::CCStr_t htmlGui::m_js=".js";	
	const size_t htmlGui::m_post_buffer_size = 512;
	const size_t htmlGui::m_max_name_size = 20;
	const size_t htmlGui::m_max_answer_size = 512;
	htmlGui::MapSS_t htmlGui::m_mimetypes;
	htmlGui::CCStr_t htmlGui::m_error_page="\
		<html>\
			<head>\
				<title>webserver derived class</title>\
				<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\
			</head>\
			<body  bgcolor=\"Red\">\
				<h2>Contenido solicitado no exitente.</h2><br><br>\
				<h1>SALUD !!</h1>\
			</body>\
		</html>";



	/*--------------------------------------------------------------------------------------------------------------
    * CONSTRUCTORs & DESTRUCTORs
    *--------------------------------------------------------------------------------------------------------------*/ 
	htmlGui::htmlGui(const Str_t &conf_path, const Str_t &confmap_path, const int port) 
    {		
		m_file_config.Load(conf_path);
		m_file_maps.Load(confmap_path);
		setDirSep(conf_path);
		Str_t l_port;
		if (m_file_config.GetAttValFromFirst(m_file_config.mc_et_TermianlProgram, m_file_config.mc_att_listen_port, l_port) == true)
		{
			Buffer_t ss(l_port);
			int l_port = 0;
			ss >> l_port;
			webServer::Set_port( l_port );

		} else webServer::Set_port( port );
		Buffer_t ss;
		ss << "http://localhost:" << webServer::Get_port();
		htmlGui::m_localhost.append(ss.str());
		m_isBinaryFile[m_jar] = true;
		m_isBinaryFile[m_html] = false;
		m_isBinaryFile[m_htm] = false;
		m_isBinaryFile[m_css] = false;
		m_mimetypes["jar"] = "application/zip";
		m_mimetypes["html"] = "text/html";
		m_mimetypes["css"] = "text/css";
		m_mimetypes["js"] = "text/javascript";
		m_mimetypes["jpeg"] = "image/jpeg";
		m_mimetypes["gif"] = "image/gif";

    }
    
    htmlGui::~htmlGui()
    {
        if (m_fileInput.is_open())
            m_fileInput.close();
    }    


	/*--------------------------------------------------------------------------------------------------------------
    * METHODS
    *--------------------------------------------------------------------------------------------------------------*/ 

	bool htmlGui::setDirSep(const Str_t& path)
	{
		Str_t like_unix = path.substr(0, 1);
		Str_t like_win  = path.substr(1, 2);
		
		if ( like_unix.compare("/") == 0 )
			m_dir_sep = m_unix_sep;
		else if ( like_win.compare(":\\") == 0 ) 
			m_dir_sep = m_win_sep;

		return true;
	}
	
    bool htmlGui::Run()
    {
		if (m_file_config.IsEmpty() == true) return false;
        if (m_file_maps.IsEmpty() == true) return false;
        webServer::m_daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, static_cast<uint16_t>(m_port), 
                                               htmlGui::on_client_connect, (void*)this, htmlGui::answer_to_connection, 
                                               (void*)this, MHD_OPTION_NOTIFY_COMPLETED, htmlGui::request_completed, 
                                               (void*)this, MHD_OPTION_END);
        
		std::printf("MaskHTML Server listening on : %d\n Press 'q' + INTRO to Exit\nUrl: %s\n", m_port, m_localhost.c_str());		
		while (std::cin.get() != 'q');
        return true;
    }

	void htmlGui::ExecuteSystemCmd(const Str_t& cmd)
	{	
		std::printf("Begining to Execute Command...");	
		Str_t lcmd(cmd);
		#ifdef WIN			
			std::ofstream FileOutput;			
			FileOutput.open("cmd.bat");
			FileOutput << "@ECHO OFF" << std::endl;
			FileOutput << lcmd << std::endl;
			FileOutput << "EXIT /B";
			FileOutput.close();		
			lcmd = "cmd.bat";
		#endif
			system(lcmd.c_str());

		readTextFile("resultado.log", m_result_file_log);
        if (m_result_file_log.size() > 200)
            m_result_file_log = m_result_file_log.substr(m_result_file_log.size()-200, 200);
	}

    void htmlGui::openBrowser()
    {
		Str_t name_of_default_page;
		m_file_config.GetAttValFromFirst(m_file_config.mc_et_HtmlGui, m_file_config.mc_att_name_of_default_page, name_of_default_page);
		Str_t url(m_localhost + m_url_sep + name_of_default_page);
		#ifdef LINUX
			std::printf("Sistema Linux dectado... Intentando abrir navegagor firefox o pestañana(compruebe el navegador) en el mismo con la url: %s\n", m_localhost.c_str());
			ExecuteSystemCmd(Str_t("firefox " + url));
		#endif    
		#ifdef WIN
			std::printf("Sistema Windows dectado... Intentando abrir navegagor firefox o pestañana(compruebe el navegador) en el mismo con la url: %s\n", m_localhost.c_str());
			ExecuteSystemCmd(url);			
		#endif
		#ifdef APPLE
			std::printf("Sistema Apple dectado... Intentando abrir navegagor firefox(o similar) o pestañana(compruebe el navegador) en el mismo con la url: %s\n", m_localhost.c_str());
			ExecuteSystemCmd(Str_t("open " + url));
		#endif   
    }
    

    int htmlGui::on_client_connect(void* , const sockaddr* addr, socklen_t )
    {
		        
		#if defined LINUX || APPLE
			//char buf[INET6_ADDRSTRLEN];
			//std::printf("IP from Client Request : %s\n" , inet_ntop(addr->sa_family, (addr->sa_data + 2), buf, INET6_ADDRSTRLEN));   
		#endif		
		#ifdef WIN
			/*
			char buf[INET6_ADDRSTRLEN];
			std::cout << "IP Client : " <<
			w32_inet_ntop(addr->sa_family, (void*)(addr->sa_data + 2), Str_t(buf), INET6_ADDRSTRLEN) << std::endl << std::endl;   
			*/
		#endif
		
		return MHD_YES;
    }
    
    int htmlGui::answer_to_connection(void* , struct MHD_Connection* connection, const char* url, const char* method, 
                                      const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls)
    {
		std::printf("%s %s %s\n", version, method, url);
		Str_t smethod(method);    
		if (*con_cls == NULL)
		{
			Connection_info_struct_t* con_info = new Connection_info_struct_t;        
			if (con_info == NULL) return MHD_NO;
			con_info->answerstring.clear();

			if (smethod.compare(m_post) == 0)
			{
				con_info->postprocessor = MHD_create_post_processor(connection, m_post_buffer_size, htmlGui::iterate_post, (void *) con_info);
				if (con_info->postprocessor == NULL)
				{
					std::printf("No se han obtenido datos del envio por POST.\n");
					delete con_info;
					return MHD_NO;
				}
				con_info->connectiontype = POST;
			}
			else if (smethod.compare(m_get) == 0)			
				con_info->connectiontype = GET;			

			*con_cls = (void *) con_info;

			return MHD_YES;
		}

		if (smethod.compare(m_get) == 0)
		{
			Str_t page, mimetype;
			htmlGui::page_dispatcher(Str_t(url), page, mimetype);
			return send_page (connection, page, mimetype);
		}

		if (smethod.compare(m_post) == 0)
		{
			Connection_info_struct_t* con_info = reinterpret_cast<Connection_info_struct_t*>(*con_cls);

			if (*upload_data_size != 0)
			{
				MHD_post_process (con_info->postprocessor, upload_data, *upload_data_size);
				*upload_data_size = 0;
	            
				return MHD_YES;
			}
			else if (!con_info->post_key_value.empty())
			{     				
				htmk::utils::Vfile_t files;
				htmk::createFileXml* mkXml = NULL;
				switch (FuncSelector(con_info->post_key_value))
				{
					case EXECUTE:
						ExecuteCmd(con_info->post_key_value, con_info->answerstring);		
						return send_page (connection, con_info->answerstring, "text/html");
						break;
					case LIST_FILES:						
						listFiles(con_info->post_key_value["ruta"].c_str(), *con_info->post_key_value["tipo"].c_str(), files);						
						mkXml = new createFileXml(files);
						mkXml->Conv();
						con_info->answerstring = mkXml->ToString();
						if(mkXml) { delete mkXml; mkXml = NULL; }
						return send_page (connection, con_info->answerstring, "text/html");
						break;
					case SEL_ERROR:
						std::printf("No se han detectado parametros en el POST.\n");
						return send_page (connection, con_info->answerstring, "text/html");
						break;
				}				
			}
		}
		Str_t page, mimetype;
		htmlGui::page_dispatcher("", page, mimetype);
		return send_page (connection, page, mimetype); 	
    }
    

	int htmlGui::FuncSelector(MapSS_t post_key_value)
	{
		if (post_key_value["cmd"].compare("list_files") == 0)
			return LIST_FILES;
		else if (post_key_value["cmd"].compare("execute") == 0)
			return EXECUTE;

		return SEL_ERROR;
	}

	bool htmlGui::listFiles(htmk::utils::CoCStr_t path, htmk::utils::CoChar_t tipo, htmk::utils::Vfile_t& files)
	{
		return htmk::utils::ListFiles(path, tipo, files);		
	}

	bool htmlGui::Files2XML(htmk::utils::Vfile_t& file, Str_t xmlResponse)
	{
		return true;
	}

	bool FuncSort1(const htmlGui::CmdArgsSorted_t& left, const htmlGui::CmdArgsSorted_t& right)
	{
		return left.position < right.position;
	}

	void htmlGui::sortArgs(const MapSS_t& data, VCmdArgsSorted_t& vArgs)
	{
		for(MapSS_t::const_iterator it = data.begin(); it != data.end(); it++)
		{
			Str_t PosValue;
			Buffer_t conv;
			size_t pos = 0;
			m_file_maps.GetAttValFromFirst(it->first, m_file_maps.mc_att_position, PosValue);
			conv << PosValue;
			conv >> pos;
			CmdArgsSorted_t wa = {pos, it->first};
			vArgs.push_back(wa);
		}
		std::sort(vArgs.begin(), vArgs.end(), FuncSort1);
	}

	void htmlGui::ExecuteCmd(const MapSS_t& data, Str_t& send_page)
	{		
		VCmdArgsSorted_t CmdArgsSorted;
		Str_t prog_name, format, parameter_type;
		Buffer_t buffer;
		m_file_config.GetAttValFromFirst(m_file_config.mc_et_TermianlProgram, m_file_config.mc_att_fullPath, prog_name);
		if (prog_name.find(" ") != std::string::npos)
			buffer << "\"" << prog_name << "\" ";
		else
			buffer << prog_name;
		sortArgs(data, CmdArgsSorted);
		for(VCmdArgsSorted_t::iterator it = CmdArgsSorted.begin(); it != CmdArgsSorted.end(); it++)
		{			
			m_file_maps.GetAttValFromFirst(it->arg, m_file_maps.mc_att_format, format);
			m_file_maps.GetAttValFromFirst(it->arg, m_file_maps.mc_att_parameter_type, parameter_type);
			if (parameter_type.compare(m_file_maps.mc_val_both) == 0)
			{
				const size_t len = format.size() + it->arg.size() + (data.find(it->arg))->second.size() + 10;
				char* ArgValue = new char[len];
				std::memset(ArgValue, 0, len);
				std::sprintf(ArgValue, format.c_str(), it->arg.c_str(), (data.find(it->arg))->second.c_str());						
				buffer << " " << ArgValue << " ";
				delete[] ArgValue;
				ArgValue = NULL;
			}
			else if (parameter_type.compare(m_file_maps.mc_val_param) == 0)
			{
				const size_t len = format.size() + it->arg.size() + 10;
				char* ArgValue = new char[len];
				std::memset(ArgValue, 0, len);
				std::sprintf(ArgValue, format.c_str(), it->arg.c_str());						
				buffer << " " << ArgValue << " ";
				delete[] ArgValue;
				ArgValue = NULL;
			}
			else if (parameter_type.compare(m_file_maps.mc_val_value) == 0)
			{
				const size_t len = format.size() + (data.find(it->arg))->second.size() + 10;
				char* ArgValue = new char[len];
				std::memset(ArgValue, 0, len);				
				std::sprintf(ArgValue, format.c_str(), (data.find(it->arg))->second.c_str());						
				buffer << " " << ArgValue << " ";
				delete[] ArgValue;
				ArgValue = NULL;
			}
		}
		buffer << " > resultado.log";
		ExecuteSystemCmd(buffer.str());
        std::printf("\n\n%s\n\n", buffer.str().c_str());
		send_page = m_result_file_log;
		std::printf("Finish.\n");		
	}

	int htmlGui::send_page (struct MHD_Connection* connection, const Str_t& page, const Str_t& mimetype)
	{
		int ret = 0;
		struct MHD_Response* response = NULL;
		
		response = MHD_create_response_from_data(page.size(), (void*) page.c_str(), MHD_NO, MHD_YES);
		//response = MHD_create_response_from_data(page.size(), (void*) page.c_str(), MHD_NO, MHD_NO);
		if (!response) return MHD_NO;
		MHD_add_response_header (response, "Content-Type", mimetype.c_str());
		ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
		MHD_destroy_response (response);

		return ret;    
	}

    void htmlGui::request_completed(void* , struct MHD_Connection* , void** con_cls, enum MHD_RequestTerminationCode )
    {
        Connection_info_struct_t* con_info = reinterpret_cast<Connection_info_struct_t*>(*con_cls);		

		if (con_info->connectiontype == POST && con_info != NULL)
		{
			MHD_destroy_post_processor(con_info->postprocessor);
			con_info->answerstring.clear();            
		}

		delete con_info;
		*con_cls = NULL;
    }

	int htmlGui::iterate_post ( void* coninfo_cls, enum MHD_ValueKind , const char* key, const char* , const char* ,
								const char* , const char* data, uint64_t , size_t )
	{
        
		Connection_info_struct_t* con_info = reinterpret_cast<Connection_info_struct_t*>(coninfo_cls);
		static size_t num_of_param = 0;
		num_of_param++;
		std::printf( "POST REQUEST [ %s, %s ]\n", key, data);

		Str_t First;
		if (m_file_maps.GetPairFirst(Str_t(key), First) == true)
			con_info->post_key_value[First] = Str_t(data);
		else if ( Str_t(key).compare("ruta") == 0 || Str_t(key).compare("tipo") == 0 )
			con_info->post_key_value[key] = Str_t(data);
		else if (Str_t(key).compare("cmd") == 0)
			con_info->post_key_value[key] = Str_t(data);
		
		return (num_of_param == m_file_maps.NumOfArgs()) ? MHD_NO : MHD_YES;		
	}

	void htmlGui::page_dispatcher(const Str_t& page_request, Str_t& page_return, Str_t& mimetype)
	{				
		Str_t root_html_path, root_folder;
		m_file_config.GetAttValFromFirst(m_file_config.mc_et_HtmlGui, m_file_config.mc_att_root_html_path, root_html_path);
		m_file_config.GetAttValFromFirst(m_file_config.mc_et_HtmlGui, m_file_config.mc_att_root_folder, root_folder);
		Buffer_t ss;

		if (page_request.compare(m_url_sep) == 0)
		{
			Str_t name_of_default_page;
			m_file_config.GetAttValFromFirst(m_file_config.mc_et_HtmlGui, m_file_config.mc_att_name_of_default_page, name_of_default_page);			
			ss << root_html_path << root_folder << m_dir_sep;
			
			mimetype = m_mimetypes["html"];
			if (htmlGui::readTextFile(ss.str() + name_of_default_page, page_return) == false)
				page_return.append(m_error_page);
			
		}
		else
		{	
			ss << root_html_path << root_folder;
			if (page_request.size() == 0)
			{
			}
			else if (m_isBinaryFile[page_request.substr(page_request.find_last_of("."), page_request.size()).c_str()] == true)
			{
				if (htmlGui::readBinaryFile(ss.str() + page_request, page_return) == false)
				{
					page_return.append(m_error_page);
					mimetype = m_mimetypes["html"];
				}
				else
				{
					mimetype = m_mimetypes[htmk::utils::ExtractEXTENSION(page_request)];
				}
			}
			else
			{
				if (htmlGui::readTextFile(ss.str() + page_request, page_return) == false)
				{
					page_return.append(m_error_page);
					mimetype = m_mimetypes["html"];
				}
				else
				{
					mimetype = m_mimetypes[htmk::utils::ExtractEXTENSION(page_request)];
				}
			}			
		}
	}

	htmlGui::Str_t htmlGui::FixWinPath(const Str_t& path)
	{
		size_t len = path.size();
		if (len == 0) return "";
		Str_t n_path;
		n_path.resize(len+1);
		char* c_str = new char[len+1];		
		memset(c_str, 0, len+1);
		strncpy(c_str, path.c_str(), len);
		size_t x = 0;
		for(size_t i = 0; i < len; i++)
		{
			if (*(c_str+i) == *m_win_sep && *(c_str+i+1) != *m_win_sep)
			{
				n_path.resize(n_path.size()+1);
				n_path[x++] = (*m_win_sep);
			}
			n_path[x++] = *(c_str+i);
		}
		delete[] c_str;
		c_str = NULL;

		return n_path;
	}
    
	bool htmlGui::readTextFile(const Str_t& path, Str_t& file)
	{			
		//m_fileInput.open(FixWinPath(path).c_str());
		m_fileInput.open( htmk::utils::FixWinPath(path).c_str() );
        if (m_fileInput.is_open())
        {            
			Str_t line;
			m_fileInput.seekg(0, std::ios::beg);
			Buffer_t ss;
			ss << m_fileInput.rdbuf();
			file = ss.str();
			m_fileInput.close();
			if (file.size() == 0) return false;
		
		} else return false;

		return true;
	}

	bool htmlGui::readBinaryFile(const Str_t& path, Str_t& file)
	{			
		//std::ifstream BinFile (FixWinPath(path).c_str(), std::ios::in | std::ios::binary | std::ios::ate);
		std::ifstream BinFile (htmk::utils::FixWinPath(path).c_str(), std::ios::in | std::ios::binary | std::ios::ate);
		std::ifstream::pos_type fileSize = 0;
		CStr_t fileContents = NULL;
		if (BinFile.is_open())
		{
			fileSize = BinFile.tellg();
			fileContents = new char[fileSize];
			BinFile.seekg(0, std::ios::beg);
			if (!BinFile.read(fileContents, static_cast<std::streamsize>(fileSize) )) 
			{
				delete[] fileContents;
				fileContents = NULL;
				return false;
			}
			BinFile.close();
			file.append(fileContents, static_cast<size_t>(fileSize) );
			delete[] fileContents;
			fileContents = NULL;		
		} else return false;

	return true;    
}

}
